package com.yckj.ytracker.api.trace;

import com.alibaba.fastjson.JSON;
import com.yckj.ytracker.api.config.Global;
import com.yckj.ytracker.api.enums.TrackerStatus;
import com.yckj.ytracker.api.enums.TrackerType;
import com.yckj.ytracker.api.model.Span;
import com.yckj.ytracker.api.model.TrackerData;
import com.yckj.ytracker.api.util.StackUtil;
import com.yckj.ytracker.api.util.TrackerHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author liuwei
 * @version 2017/10/14 下午3:12
 * @description 功能描述
 * @see
 * @since 1.0
 */
public class TracerClient implements ITracerClient {
    private static final Logger LOGGER = LoggerFactory.getLogger(TracerClient.class);

    private static AtomicInteger interval = new AtomicInteger(1);
    private static ThreadLocal<Boolean> isInSample = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

    private TrackerType trackerType;

    public TracerClient(TrackerType trackerType) {
        this.trackerType = trackerType;
        try {
            interval.set(Global.getInstance().getSample());
            TrackerHelper.init();
        } catch (Exception e) {
            LOGGER.error("YTracker: init dtracker error :{}", e);
        }
    }

    @Override
    public void startSpan(String methodName, String clusterName, String param) {
        try {
            if (Global.getInstance().isClosed()) {
                LOGGER.debug("YTracker: config tracker closed!");
                return;
            }
            if (methodName == null || clusterName == null || methodName.length() <= 0 || clusterName.length() <= 0) {
                LOGGER.error("YTracker: methodName: {} or clusterName: {} is null!", methodName, methodName);
                return;
            }
            ITracerServer tracer = TrackerHelper.getTracer();
            Span currentSpan = TrackerHelper.getCurrentSpan();
            if (null != currentSpan) {
                tracer.startChildSpan(trackerType.getValue(), methodName, param);
            } else {
                if (!isInSample()) {
                    LOGGER.error("YTracker: not in sample!");
                    return;
                }
                tracer.startNewSpan(clusterName, trackerType.getValue(), methodName, param);
            }
            tracer.setSpanSent();
        } catch (Exception e) {
            LOGGER.error("YTracker: start span error! methodName : {}, clusterName : {}, param : {}", methodName, clusterName, param, e);
        }
    }

    @Override
    public void offSpan(TrackerStatus trackerStatus) {
        this.offSpan(trackerStatus, null);
    }

    @Override
    public void offSpan(TrackerStatus trackerStatus, Throwable throwable) {
        this.offSpan0(trackerStatus, throwable);
    }

    private void offSpan0(TrackerStatus stat, Throwable t) {
        try {
            if (Global.getInstance().isClosed()) {
                LOGGER.error("YTracker: config tracker closed");
                return;
            }
            if (stat == null) {
                LOGGER.error("YTracker: null tracker stat");
                return;
            }
            ITracerServer tracer = TrackerHelper.getTracer();
            try {
                Span span = TrackerHelper.getCurrentSpan();
                if (null != span) {
                    span.setState(stat.getKey());
                    span.setStack(StackUtil.getStack(t));
                    tracer.setSpanReceived();
                }
            } catch (Exception e) {
                tracer.clearCurrentSpan();
            }
        } catch (Exception e) {
            LOGGER.error("YTracker: off span error!", e);
        }
    }

    @Override
    public void setAttributes(Map<String, String> attributes) {
        try {
            if (Global.getInstance().isClosed()) {
                LOGGER.error("YTracker: config tracker closed");
                return;
            }
            if (attributes == null || attributes.size() <= 0) {
                LOGGER.error("YTracker: attributes map empty: {}", attributes);
                return;
            }
            Span currentSpan = TrackerHelper.getCurrentSpan();
            if (currentSpan != null) {
                currentSpan.setAttributes(attributes);
            }
        } catch (Exception e) {
            LOGGER.error("YTracker: set attributes error! attributes : {}", attributes, e);
        }
    }

    @Override
    public void close() {
        try {
            if (Global.getInstance().isClosed()) {
                LOGGER.error("YTracker: config tracker closed");
                return;
            }
            if (trackerType.getKey() % 2 == 1) {
                LOGGER.debug("YTracker: close all span cause of a server span : {}", trackerType.getValue());
                TrackerHelper.getTracer().collectAllSpan();
            }
        } catch (Exception e) {
            LOGGER.error("YTracker: close error!:{}", e);
        }
    }

    @Override
    public String getTrackerData() {
        TrackerData trackerData = new TrackerData();
        String trackerJson = "{}";
        try {
            if (Global.getInstance().isClosed()) {
                LOGGER.debug("YTracker: config tracker closed");
                return JSON.toJSONString(trackerData);
            }
            Span span = TrackerHelper.getCurrentSpan();
            if (span != null) {
                trackerData.setSpanId(span.getSpanId() + ".1");
                trackerData.setTraceId(span.getTraceId());
                trackerData.setRootName(span.getRootName());
            }
            trackerJson = JSON.toJSONString(trackerData);
            LOGGER.debug("YTracker: tracker json to serialize {}", trackerJson);
        } catch (Exception e) {
            LOGGER.error("YTracker: get tracker data error!:{}", e);
        }
        return trackerJson;
    }

    private boolean isInSample() {
        if (Global.getInstance().getSample() == 1) {
            isInSample.set(true);
        } else {
            if (interval.compareAndSet(1, 1)) {
                interval.getAndIncrement();
                isInSample.set(true);
            } else if (interval.getAndIncrement() < Global.getInstance().getSample()) {
                isInSample.set(false);
            } else {
                interval.getAndSet(1);
                isInSample.set(false);
            }
        }
        return isInSample.get();
    }
}
